import moment from 'moment';
import _ from 'lodash';
import koaLog4 from 'koa-log4';
import { USER_GROUP_TEACHER, USER_GROUP_STUDENT } from '../common/groups';
import mongooseInit from '../../components/mongoose';
import Group from '../models/group';
import Channel from '../models/channel';
import ChannelUser from '../models/channelUser';
import User from '../models/user';
import channelHandler from '../handler/channelHandlers';
import waitHandler from '../handler/waitHandler';

const KICK_TIME = 30;

if (!module.parent) {
  koaLog4.configure({
    appenders: [
      { type: 'console' },
      {
        type: 'dateFile',
        filename: 'log/kicker.log',
        pattern: '-yyyy-MM-dd',
        category: 'Channel-Kicker',
      },
    ],
  });
}

const logger = koaLog4.getLogger('Channel-Kicker');
class ChannelKicker {
  /**
   * @param {ChannelHandler} _channelHandler
   * @param {WaitHandler} _waitHandler
   */
  constructor(_channelHandler, _waitHandler) {
    this.channelHandler = _channelHandler;
    this.waitHandler = _waitHandler;
  }

  findStudentAndTeacherGroup() {
    return Group.find({ name: { $in: [USER_GROUP_STUDENT, USER_GROUP_TEACHER] } });
  }

  findGroupUnActiveChannel(groups) {
    // [[], []]
    const channelsInGroupPromises = groups.map(group =>
      Channel.find({
        group,
        'members.1': { $exists: true },
        lastActiveAt: { $lt: moment().subtract(KICK_TIME, 'minutes') },
      })
        .populate('group')
        .then(channels =>
          channels.map(channel => ({
            channel,
            group,
          }))));

    // []
    return Promise.all(channelsInGroupPromises).then(result => _.flatten(result));
  }

  findChannelsUsers(channels) {
    // [[], [], []]
    const channelUsersPromises = channels.map(({ channel, group }) =>
      ChannelUser.find({ channel })
        .populate('user')
        .then(channelUsers =>
          channelUsers.map(channelUser => ({
            channel,
            group,
            channelUser,
            user: channelUser.user,
          }))));

    return Promise.all(channelUsersPromises).then(result => _.flatten(result));
  }

  async tryKickUser({
    channel, channelUser, user, group,
  }) {
    // 不踢出客户
    if (user.isCustomer()) return Promise.resolve(null);
    // 不踢出标星
    if (channelUser.mark) return Promise.resolve(null);
    try {
      await this.doKick(channelUser, channel, group, user);
    } catch (e) {
      logger.debug('Kick | kick fail with', e);
      throw e;
    }

    return Promise.resolve();
  }

  async doKick(channelUser, channel, group, user) {
    if (channelUser.unreadCount > 0) {
      const lastMessage = await this.waitHandler.getLastMessage(channel);

      if (lastMessage.fromUser.toString() !== channel.customer.toString()) {
        logger.debug(`Channel ${channel.id} | ${user.jiuquId} has unread message, but not from customer, skip`);
        return;
      }

      logger.debug(`Channel ${channel.id} | ${user.jiuquId} has unread message, push back to waitList`);
      await this.waitHandler.add(channel, group);
    }
    try {
      logger.debug(`Channel ${channel.id} | remove unActive user ${user.id} with jiuquId: ${user.jiuquId}`);
      await this.channelHandler.leave(channel, user);
    } catch (e) {
      logger.error(`Channel ${channel.id} | remove unActive user ${user.id} with jiuquId: ${user.jiuquId} fail with`, e, 'Try fresh model...');
      const [freshChannel, freshUser] = await Promise.all([
        Channel.findById(channel.id),
        User.findById(user.id),
      ]);
      await this.channelHandler.leave(freshChannel, freshUser);
    }
    logger.debug(`Channel ${channel.id} | remove unActive user ${user.jiuquId} success`);
  }

  kickChannelUsers(channelUsersInfo) {
    return Promise.all(channelUsersInfo.map(this.tryKickUser.bind(this)));
  }

  run() {
    mongooseInit();

    return this.findStudentAndTeacherGroup()
      .then(this.findGroupUnActiveChannel)
      .then(this.findChannelsUsers)
      .then(this.kickChannelUsers.bind(this));
  }
}

const channelKicker = new ChannelKicker(channelHandler, waitHandler);

if (!module.parent) {
  // run in script
  channelKicker
    .run()
    .then(() => {
      logger.debug('    ########### Channel Kick Success #########\n');
      process.exit();
    })
    .catch((e) => {
      logger.error(e);
      process.exit(1);
    });
}

export default channelKicker;
